import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.*;
import java.lang.reflect.*;

/**
 * This class represents a Button.
 * <br /><br />
 * It is responsible for providing a Domain Specific Action Control.
 * 
 * @author (Dilyan GEORGIEV)
 * @author (Thomas KOBER)
 * @author (Matthew MCDONALD)
 * 
 * @version (1.0)
 */
public class Button extends Actor implements Observable
{
    ////////////////////////////////////////////////////////////////////////////////
    // Field Declarations
    ////////////////////////////////////////////////////////////////////////////////
    
    // The associated Difficulty Model Value
    private Difficulty difficulty;
    // The Button Type
    private ButtonType buttonType;
    // The List of Observers
    private ArrayList<GameObserver> observers;
    
    ////////////////////////////////////////////////////////////////////////////////
    // Constructor(s)
    ////////////////////////////////////////////////////////////////////////////////
    
    /**
     * Constructor for objects of class Button.
     * @param buttonType the Buttons Type.
     */
    public Button(ButtonType buttonType)
    {
        super();
        
        this.buttonType = buttonType;
        this.observers = new ArrayList<GameObserver>();
    }
    
    /**
     * Returns the Buttons associated Game Difficulty (if any).
     * @return the Buttons associated Game Difficulty (if any).
     */
    public Difficulty getDifficulty()
    {
        return this.difficulty;
    }
    
    /**
     * Sets the Buttons associated Game Difficulty to the given value.
     * @param difficulty the Game Difficulty to connect the Button with.
     */
    public void setDifficulty(Difficulty difficulty)
    {
        this.difficulty = difficulty;
    }
    
    /**
     * Returns the Buttons Type.
     * @return the Buttons Type.
     */
    public ButtonType getButtonType()
    {
        return this.buttonType;
    }
    
     /**
     * Act - do whatever the MessageBox wants to do. This method is called whenever
     * the 'Act' or 'Run' button gets pressed in the environment.
     */
    public void act() 
    {
        // Handle Click Events
        MouseInfo mouse = Greenfoot.getMouseInfo();
        
        if (mouse == null) return;
        if (mouse.getActor() == this && mouse.getButton() == 1)  {
            
            Class[] difficultyParams = {Difficulty.class};
            Class[] yesNoParams = null;
            
            Object[] difficultyMethodParams = {this.difficulty};
            Object[] yesNoMethodParams = null;
            
            try {
                
                switch (this.buttonType) {
                    case DIFFICULTY:
                        this.notifyObservers(GameObserver.class.getDeclaredMethod("handleDifficultySelected", difficultyParams), difficultyMethodParams);
                        break;
                        
                    case YES:
                        this.notifyObservers(GameObserver.class.getDeclaredMethod("handlePlayAgainSelected", yesNoParams), yesNoMethodParams);
                        break;
                       
                    case NO:
                        this.notifyObservers(GameObserver.class.getDeclaredMethod("handleGiveUpSelected", yesNoParams), yesNoMethodParams);
                        
                    default:
                        break;
                }
                
            } catch (NoSuchMethodException ex) {
                ex.printStackTrace();
            }
        }
    }  
    
    ////////////////////////////////////////////////////////////////////////////////
    // Observable Interface Implementation
    ////////////////////////////////////////////////////////////////////////////////
    
    /**
     * Notifies the Objects' Observers about an update.
     * @param theMethod the update Method to be invoked.
     * @param args the corresponding Parameter List of theMethod.
     */
    public void notifyObservers(Method theMethod, Object... args)
    {
        try {
            for (GameObserver o : this.observers) {
                if (args != null) {
                    theMethod.invoke(o, args);
                } else {
                    theMethod.invoke(o);
                }
            }
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        }
    }
    
    /**
     * Adds the given GameObserver to the List of Observers of this object.
     * @param o the GameObserver to be added to the List of Observers.
     */
    public void addObserver(GameObserver o)
    {
        this.observers.add(o);
    }
    
    /**
     * Removes the given GameObserver from the List of Observers of this object.
     * @param o the GameObserver to be removed from the List of Observers.
     */
    public void removeObserver(GameObserver o)
    {
        this.observers.remove(o);
    }
}
